﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.IO;
using System.Net.NetworkInformation;
using System.Net;

namespace MapReduce.Services
{
    public class CheckConfigService : IService
    {
        protected PerformanceCounter mCpu;
        protected PerformanceCounter mRam;

        public CheckConfigService()
        {
            mCpu = new PerformanceCounter("Processor", "% Processor Time");
            //mCpu.CategoryName = "Processor";
            //mCpu.CounterName = "% Processor Time";
            mCpu.InstanceName = "_Total";
            mRam = new PerformanceCounter("Memory", "Available MBytes");
        }

        public byte[][] Execute(byte[][] pData)
        {

            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(U.Convert(pData[0]).ToString());

                if (doc.DocumentElement == null || doc.DocumentElement.InnerXml == "")
                {
                    throw new Exception("Config xml not valid.");
                }

                XmlNodeList nodes = doc.SelectNodes("Config/*");
                for (int i = 0; i < nodes.Count; i++)
                {
                    XmlNode node = nodes[i];
                    switch (node.Name)
                    {
                        case "Cpu":
                            {
                                ValidateCpu(node);
                            }
                            break;
                        case "Memory":
                            {
                                ValidateMemory(node);
                            }
                            break;
                        case "Folder":
                            {
                                ValidateFolder(node);
                            }
                            break;
                        case "File":
                            {
                                ValidateFile(node);
                            }
                            break;
                        case "Ping":
                            {
                                ValidatePing(node);
                            }
                            break;

                    }
                }

                return new byte[][]{U.GetBytes("1")};
            }
            catch (Exception ex)
            {
                return new byte[][]{U.GetBytes(ex.Message)};
            }
        }//Execute


        //Function source from http://msdn.microsoft.com/en-us/library/system.net.networkinformation.ping.aspx
        private void ValidatePing(XmlNode pNode)
        {
            String hostName = pNode.InnerText;
            String upperHostName = hostName.ToUpper();

            PingReply reply=null;

            try
            {

                //Test web request first
                if (upperHostName.StartsWith("HTTP://") || upperHostName.StartsWith("HTTPS://"))
                {
                    //Prepare the HTTP URL request
                    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(hostName);

                    //Execute the request
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                    Stream resStream = response.GetResponseStream();
                    resStream.ReadByte();

                    //Nothing crashed. Good!
                    resStream.Close();
                    response.Close();
                    return;
                }
                else
                {


                    Ping pingSender = new Ping();
                    PingOptions options = new PingOptions();

                    // Use the default Ttl value which is 128,
                    // but change the fragmentation behavior.
                    options.DontFragment = true;

                    // Create a buffer of 32 bytes of data to be transmitted.
                    string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
                    byte[] buffer = Encoding.ASCII.GetBytes(data);
                    int timeout = 120;
                    reply = pingSender.Send(hostName, timeout, buffer, options);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + " Hostname:" + hostName);
            }

            if (reply.Status == IPStatus.Success)
            {
                //U.Log("Address: " + reply.Address.ToString());
                //U.Log("RoundTrip time: " + reply.RoundtripTime);
                //U.Log("Time to live: " + reply.Options.Ttl);
                //U.Log("Don't fragment: " + reply.Options.DontFragment);
                //U.Log("Buffer size: " + reply.Buffer.Length);
            }
            else
            {
                throw new Exception("Can not Ping host '" + hostName + "'");
            }
        }//ValidatePing


        private void ValidateFile(XmlNode pNode)
        {
            String strPath = pNode.InnerText;
            if (!File.Exists(strPath)) throw new Exception("'" + strPath + "' file either does not exist or this node doesn't have access to it.");

            File.ReadAllBytes(strPath);

        }//ValidateFile


        private void ValidateFolder(XmlNode pNode)
        {
            String strPath = pNode.InnerText;
            if (!Directory.Exists(strPath)) throw new Exception("'" + strPath + "' Folder either does not exist or this node doesn't have access to it.");


            //Check read access first. Just get the files list of this directory.
            Directory.GetFiles(strPath);

            //Check the write access
            XmlAttribute accessAttribute = pNode.Attributes["Access"];

            bool checkWrite = true;

            if (accessAttribute != null) checkWrite = accessAttribute.InnerText.Contains("W");

            if (checkWrite)
            {
                //Try creating a folder in this directory. 
                String subDirName = Path.Combine(strPath, "_MR_Test_Dir_" + Guid.NewGuid().ToString());

                Directory.CreateDirectory(subDirName);

                //Looks good so far. Now write a file in that sub directory.
                String testFile = Path.Combine(subDirName, "_MR_Test_File_" + Guid.NewGuid().ToString() + ".txt");
                File.WriteAllText(testFile, "test");

                //Again good so far. Now delete the file
                File.Delete(testFile);

                //Now delete the sub directory
                Directory.Delete(subDirName);
            }


        }//ValidateFolder

        private void ValidateMemory(XmlNode pNode)
        {
            String strMinMem = pNode.InnerText;

            float minMemory = Convert2MB(strMinMem);
            float currMemory = mRam.NextValue();

            if (currMemory < minMemory)
            {
                throw new Exception("Rquired " + minMemory + "MB Ram not available. Available " + currMemory + "MB");
            }
        }//ValidateMemory



        private void ValidateCpu(XmlNode pNode)
        {
            String strMinCpu = pNode.InnerText;
            if (strMinCpu.EndsWith("%")) strMinCpu = strMinCpu.Substring(0, strMinCpu.Length - 1);

            float minCpu = -1;
            float.TryParse(strMinCpu, out minCpu);

            if (minCpu <= 0 || minCpu > 100)
            {
                throw new Exception("Invalid CPU value. The value must be a number between 1 and 100 followed by a % sign.");
            }

            float currCpu = mCpu.NextValue();

            if (currCpu > minCpu)
            {
                throw new Exception("Rquired " + minCpu + "% CPU not available. Available " + currCpu + "%");
            }
        }//ValidateCpu

        private float Convert2MB(String pVal)
        {
            bool isGB = pVal.Contains("GB");
            if (isGB)
            {
                pVal = pVal.Replace("GB", "");
            }
            else
            {
                pVal = pVal.Replace("MB", "");
            }

            float mem = -1;
            float.TryParse(pVal, out mem);
            if (mem <= 0)
            {
                throw new Exception("Invalid Memory value.");
            }

            if (isGB) mem *= 1024;

            return mem;
        }

    }
}
